<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Carousel</title>
  <script>

    function createCanvas(width = 300, height = 150) {
      const canvas = document.createElement("canvas");
      canvas.width = width;
      canvas.height = height;
      return canvas;
    }
    function randImage(
      text,
      width = 160,
      height = 160,
      bgColor = "black",
      fontColor = "white"
    ) {
      const canvas = createCanvas(width, height);
      const ctx = canvas.getContext("2d");
      ctx.fillStyle = bgColor;
      ctx.fillRect(0, 0, width, height);
      ctx.fillStyle = fontColor;
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.font = Math.min(width, height) / 2 + "px bold";
      ctx.fillText(text, width / 2, height / 2);
      return canvas.toDataURL()
    }

  </script>

  <style>
    :root {
      --container-width: 480px;
      --container-height: 240px;
      --control-width: 32px;
      --control-height: 64px;
    }

    .container {
      position: relative;
      width: var(--container-width);
      height: var(--container-height);
      background-color: #ccc;
      margin: 0 auto;
    }

    .carousel_view {
      overflow: hidden;
    }

    .item {
      width: 100%;
      height: var(--container-height);
      flex-grow: 0;
      flex-shrink: 0;
      object-fit: contain;
    }

    .carousel {
      display: flex;
      transition: 1s;
    }

    .control button {
      color: white;
      background-color: #999;
      font-size: larger;
      border: 0;
      height: var(--control-height);
      width: var(--control-width);
      position: absolute;
      top: calc(50% - var(--control-height) / 2);
      transition: .5s;
    }

    .control button:hover {
      cursor: pointer;
      background-color: #666;
    }

    .control button.left {
      left: 2%;
    }

    .control button.right {
      right: 2%;
    }
  </style>
</head>

<body>
  <h1>无缝轮播图</h1>
  <p>1. 关键技巧在于头尾复制一份拼接，利用clientWidth触发重绘</p>
  <p>2. 指定时间未手动点击，则自动轮播</p>
  <h2>问题</h2>
  <p>1. 定时器后台运行再打开时，会跳帧</p>

  <div class="container">
    <div class="carousel_view">
      <div class="carousel"></div>
    </div>
    <div class="control">
      <button class="left">&lt;</button>
      <button class="right">&gt;</button>
    </div>
  </div>

  <script>
    const dom = {
      carousel: document.querySelector(".carousel"),
      left: document.querySelector(".control .left"),
      right: document.querySelector(".control .right"),
    }

    !(function main() {
      // 初始化图片
      const COUNT = 5;
      const imgs = [];
      for (let i = 1; i <= COUNT; i++) {
        imgs.push(randImage(i, 480, 240, `rgb(${[i, i + 1, i + 2].map(i => i * 30)})`))
      }
      // 无缝，复制头尾
      dom.carousel.innerHTML = [imgs.at(-1), ...imgs, imgs[0]].map(i => `<img class="item" src="${i}" alt="" />`).join("")

      // events
      dom.left.addEventListener('click', function () {
        stopAnimation()
        moveTo(--current)
        autoStartAnimationLater()
      })
      dom.right.addEventListener('click', function () {
        stopAnimation()
        moveTo(++current)
        autoStartAnimationLater()
      })

      const SILENT_TIME = 5_000
      const INTERVAL = 1500
      let timer = null;
      let current = 1; // start from 1, because 0 is copied by the last one.
      moveTo(current)

      function startAnimation() {
        moveTo(++current)
        timer = setTimeout(() => {
          startAnimation()
        }, INTERVAL)
      }

      function stopAnimation() {
        clearTimeout(timer);
        timer = null;
      }

      let autoTimer = null;
      function autoStartAnimationLater() {
        if (autoTimer) { clearTimeout(autoTimer); autoTimer = null }
        autoTimer = setTimeout(
          startAnimation,
          SILENT_TIME
        )
      }
      autoStartAnimationLater()

      function moveTo(index) {
        console.log(index);
        if (index === imgs.length + 1) {
          dom.carousel.style.transition = 'none'
          dom.carousel.style.transform = `translateX(${0}%)`
          dom.carousel.clientWidth // trigger repaint
          current = 1;
          dom.carousel.style.transition = '1s'
          dom.carousel.style.transform = `translateX(${-100 * current}%)`
        } else if (index === 0) {
          dom.carousel.style.transition = 'none'
          dom.carousel.style.transform = `translateX(${-100 * (imgs.length + 1)}%)`
          dom.carousel.clientWidth // trigger repaint
          current = imgs.length;
          dom.carousel.style.transition = '1s'
          dom.carousel.style.transform = `translateX(${-100 * current}%)`
        } else {
          dom.carousel.style.transform = `translateX(${-100 * index}%)`
        }
      }
    })()
  </script>

</body>

</html>